package com.lock.redisLocks.support;

import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lock.redisLocks.RedisHelper.ProviderConfigType;
import com.lock.redisLocks.RedisHelper.RedisConfigType;
import com.lock.redisLocks.RedisHelper;
import com.lock.redisLocks.RedisHelper.JedisPoolWraper;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.util.Pool;

@SuppressWarnings("rawtypes")
public class RDSMonitor {
    private final static Logger logger = LoggerFactory.getLogger("jedix");
    private static Map<String, MonitorItem> items = new ConcurrentHashMap<String, MonitorItem>();
    private static Map<String, Long> dumpData = new HashMap<String, Long>();
    private static long lastUpdate = 0L;
    private static Timer rdsTimer = new Timer("RedisConnMonitorScheduler", true);

    static {
        // 每200ms秒收集一次连接池信息, 累计存储, 读取时候获取存储的平均值并清空
        rdsTimer.schedule(new DBCPConnMonitorPerSecTimer(), 0L, 200L);
        // 每两秒执行一次, 每分钟内dump一次数据
        rdsTimer.schedule(new DBCPConnDataDumpTimer(), 0L, 2000L);
    }

    public static Map<String, Long> getValues() {
        return dumpData;
    }

    /**
     * name的count计数值+count, 时间值+time, 同时计数和计时间
     *
     * @param name
     * @param count
     * @param time
     */
    private static void recordMany(String name, long count, long time) {
        MonitorItem item = (MonitorItem) items.get(name);
        if (item == null) {
            item = new MonitorItem();
            items.put(name, item);
        }
        item.add(count, time);
    }

    /**
     * 运行一次, 获取数据库连接池的名字, 连接数量, 存储在Map, Key为poolName, Value为连接数量和取样次数,
     * 获取的时候, 计算出平均, 最大, 最小等统计数据
     * 用数量/取样次数, 获取到平均连接数 当周期获取一次的时候, 就获取到每周期内的平均连接数
     */
    private static class DBCPConnMonitorPerSecTimer extends TimerTask {
        Pool<Jedis> jps = null;
        GenericObjectPoolConfig config = null;

        public void run() {
            Set<String> poolNamesSet = RedisHelper.getPoolNames();
            for (String poolName : poolNamesSet) {
                JedisPoolWraper jedisPoolWraper = RedisHelper.getJedisPoolWraper(poolName);
                if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())
                        && ProviderConfigType.LETTUCE.equals(jedisPoolWraper.getProvider())) {

                    // TODO

                } else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())
                        && ProviderConfigType.LETTUCE.equals(jedisPoolWraper.getProvider())) {
                    // TODO

                } else if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())
                        && ProviderConfigType.JEDIS.equals(jedisPoolWraper.getProvider())) {

                    jps = (JedisPool) jedisPoolWraper.getPool();
                    config = jedisPoolWraper.getPoolConfig();
                    int idleValue = jps.getNumIdle();
                    int activeValue = jps.getNumActive();
                    int waitValue = jps.getNumWaiters();
                    int totalValue = config.getMaxTotal();

                    // logger.trace("poolName={}:totalValue={}", poolName, totalValue);
                    // logger.trace("poolName={}:activeValue={}", poolName, activeValue);
                    // logger.trace("poolName={}:idleValue={}", poolName, idleValue);
                    // logger.trace("----------------------------");
                    recordMany("RDS_" + poolName + "_ConnTotal", totalValue, 1L);
                    recordMany("RDS_" + poolName + "_ConnActive", activeValue, 1L);
                    recordMany("RDS_" + poolName + "_ConnIdle", idleValue, 1L);
                    recordMany("RDS_" + poolName + "_ConnWait", waitValue, 1L);
                } else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())
                        && ProviderConfigType.JEDIS.equals(jedisPoolWraper.getProvider())) {
                    JedisCluster jedisCluster = (JedisCluster) jedisPoolWraper.getPool();
                    config = jedisPoolWraper.getPoolConfig();
                    int totalValue = config.getMaxTotal();
                    Map<String, JedisPool> infoMap = jedisCluster.getClusterNodes();
                    for (Entry<String, JedisPool> entry : infoMap.entrySet()) {
                        String key = entry.getKey();// ip:port
                        Pool<Jedis> jps = (JedisPool) entry.getValue();
                        int idleValue = jps.getNumIdle();
                        int activeValue = jps.getNumActive();
                        int waitValue = jps.getNumWaiters();
                        // logger.trace("poolName={}:{}:totalValue={}", poolName, key, totalValue);
                        // logger.trace("poolName={}:{}:activeValue={}", poolName, key, activeValue);
                        // logger.trace("poolName={}:{}:waitValue={}", poolName, key, waitValue);
                        // logger.trace("poolName={}:{}:idleValue={}", poolName, key, idleValue);
                        // logger.trace("----------------------------");
                        recordMany("RDS_" + poolName + "_" + fixKey(key) + "_ConnTotal", totalValue, 1L);
                        recordMany("RDS_" + poolName + "_" + fixKey(key) + "_ConnActive", activeValue, 1L);
                        recordMany("RDS_" + poolName + "_" + fixKey(key) + "_ConnIdle", idleValue, 1L);
                        recordMany("RDS_" + poolName + "_" + fixKey(key) + "_ConnWait", waitValue, 1L);
                    }
                }

            }
            if (logger.isTraceEnabled()) {
                for (Entry<String, MonitorItem> entry : items.entrySet()) {
                    logger.trace("SecCheck:{}", entry);
                }
                logger.trace("-------------------------");
            }
        }

        private String fixKey(String string) {
            return StringUtils.replaceChars(StringUtils.replaceChars(string, '.', '_'), ':', '_');
        }
    }

    /**
     * 每分钟dump一次数据
     *
     * @author wanghaitao
     */
    private static class DBCPConnDataDumpTimer extends TimerTask {
        @Override
        public void run() {
            long current = System.currentTimeMillis();
            // 距离上次检查间隔50秒内, 跳出
            if (current - lastUpdate < 50000L) {
                return;
            }
            Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(current);
            // 当前秒数大于10,直接跳出,也就是在每分钟的10秒内如果失败可以重试5次,如果成功, 则lastUpdate小于50秒
            if (cal.get(Calendar.SECOND) > 10) {
                return;
            }

            RDSMonitor.lastUpdate = current;
            Map<String, Long> tmpDumpData = new HashMap<String, Long>();
            // System.err.println("DUMP:" + ret);
            try {
                for (Entry<String, MonitorItem> entry : items.entrySet()) {
                    String name = makeName(entry.getKey());
                    MonitorItem item = entry.getValue().dumpAndReset();
                    long connAvgQtyPerSec = item.dumpTime > 0L ? Long.valueOf(item.dumpCount / item.dumpTime) : 0L;
                    tmpDumpData.put(name + "_Count", new Long(connAvgQtyPerSec));

                    if (name.endsWith("_ConnActive") || name.endsWith("_ConnWait")) {
                        tmpDumpData.put(name + "_MaxCount", new Long(item.dumpMaxCount));
                    }

                    // out.println(String.format("JVM_Heap_Ratio=%1$.2f", ((float) used / (float) max) * 100f));

                }
            } catch (Throwable e) {
            }

            dumpData = Collections.unmodifiableMap(tmpDumpData);
            logger.debug("DUMP:{}", dumpData);
        }

        private String makeName(String name) {
            return name.replaceAll(" ", "_");
        }
    }

    /**
     * 累计记录两个数值的对象类
     *
     * @author wanghaitao
     */
    private static class MonitorItem {
        private final LongAdder count;// 累计
        private final LongAdder time;
        private final LongAccumulator maxCount;// 单次瞬时最大值
        private final LongAccumulator maxTime;
        private final long dumpCount;
        private final long dumpTime;
        private final long dumpMaxCount;
        private final long dumpMaxTime;

        public MonitorItem() {
            super();
            this.count = new LongAdder();
            this.time = new LongAdder();
            this.maxCount = new LongAccumulator(Long::max, 0L);
            this.maxTime = new LongAccumulator(Long::max, 0L);
            this.dumpCount = 0;
            this.dumpTime = 0;
            this.dumpMaxCount = 0;
            this.dumpMaxTime = 0;
        }

        public MonitorItem(long count, long time, long maxCount, long maxTime) {
            super();
            this.count = new LongAdder();
            this.time = new LongAdder();
            this.maxCount = new LongAccumulator(Long::max, 0L);
            this.maxTime = new LongAccumulator(Long::max, 0L);
            this.dumpCount = count;
            this.dumpTime = time;
            this.dumpMaxCount = maxCount;
            this.dumpMaxTime = maxTime;

        }

        public void add(long count, long time) {
            this.count.add(count);
            this.time.add(time);
            this.maxCount.accumulate(count); // 比较value和上一次的比较值，然后存储较大者
            this.maxTime.accumulate(time);
        }

        public synchronized MonitorItem dumpAndReset() {
            return new MonitorItem(this.count.sumThenReset(), this.time.sumThenReset(), this.maxCount.getThenReset(), this.maxTime.getThenReset());
        }

        @Override
        public String toString() {
            return "MonitorItem [count=" + count.sum()
                    + ", time=" + time.sum()
                    + ", maxCount=" + maxCount.get()
                    + ", maxTime=" + maxTime.get()
                    + ", dumpcount=" + dumpCount
                    + ", dumptime=" + dumpTime
                    + ", dumpmaxCount=" + dumpMaxCount
                    + ", dumpmaxTime=" + dumpMaxTime + "]";
        }

    }//

}
